Skip to main content

libobs_simple\sources\windows\sources/
monitor_capture.rs

1//! Monitor capture source for Windows using libobs-rs
2//! This source captures the entire monitor and is used for screen recording.
3
4use std::sync::Arc;
5
6use super::ObsDisplayCaptureMethod;
7use crate::error::ObsSimpleError;
8use crate::{define_object_manager, sources::macro_helper::impl_custom_source};
9/// Note: This does not update the capture method directly, instead the capture method gets
10/// stored in the struct. The capture method is being set to WGC at first, then the source is created and then the capture method is updated to the desired method.
11use display_info::DisplayInfo;
12use libobs_simple_macro::obs_object_impl;
13use libobs_wrapper::run_with_obs;
14use libobs_wrapper::runtime::ObsRuntime;
15use libobs_wrapper::scenes::{ObsSceneItemRef, SceneItemExtSceneTrait};
16use libobs_wrapper::{
17    data::{ObsObjectBuilder, ObsObjectUpdater},
18    scenes::ObsSceneRef,
19    sources::{ObsSourceBuilder, ObsSourceRef, ObsSourceTrait},
20    unsafe_send::Sendable,
21    utils::ObsError,
22};
23use num_traits::ToPrimitive;
24use windows::Win32::UI::HiDpi::{
25    GetAwarenessFromDpiAwarenessContext, GetThreadDpiAwarenessContext,
26    SetProcessDpiAwarenessContext, DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2,
27    DPI_AWARENESS_UNAWARE,
28};
29
30// Usage example
31define_object_manager!(
32    /// Provides an easy-to-use builder for the monitor capture source.
33    #[derive(Debug)]
34    struct MonitorCaptureSource("monitor_capture", *mut libobs::obs_source) for ObsSourceRef {
35        #[obs_property(type_t = "string", settings_key = "monitor_id")]
36        monitor_id_raw: String,
37
38        #[obs_property(type_t = "bool")]
39        /// Sets whether the cursor should be captured.
40        capture_cursor: bool,
41
42        #[obs_property(type_t = "bool")]
43        /// Compatibility mode for the monitor capture source.
44        compatibility: bool,
45
46        #[obs_property(type_t = "bool")]
47        /// If the capture should force SDR
48        force_sdr: bool,
49
50        capture_method: Option<ObsDisplayCaptureMethod>,
51    }
52);
53
54#[obs_object_impl]
55impl MonitorCaptureSource {
56    /// Gets all available monitors
57    pub fn get_monitors() -> Result<Vec<Sendable<DisplayInfo>>, ObsSimpleError> {
58        Ok(DisplayInfo::all()
59            .map_err(ObsSimpleError::DisplayInfoError)?
60            .into_iter()
61            .map(Sendable)
62            .collect())
63    }
64
65    pub fn set_monitor(self, monitor: &Sendable<DisplayInfo>) -> Self {
66        self.set_monitor_id_raw(monitor.0.name.as_str())
67    }
68}
69
70fn is_thread_dpi_unaware(runtime: &ObsRuntime) -> Result<bool, ObsError> {
71    run_with_obs!(runtime, (), move || {
72        unsafe {
73            // Safety: This function can be called from any thread.
74            let ctx = GetThreadDpiAwarenessContext();
75            GetAwarenessFromDpiAwarenessContext(ctx) == DPI_AWARENESS_UNAWARE
76        }
77    })
78}
79
80fn set_dpi_awareness_if_needed(runtime: &ObsRuntime) -> Result<(), ObsError> {
81    if is_thread_dpi_unaware(runtime)? {
82        log::warn!("The current thread is DPI unaware. Setting the DPI awareness context to Per Monitor Aware V2 to allow DXGI capture method to work correctly.");
83    } else {
84        return Ok(());
85    }
86
87    let set_result = unsafe {
88        // Safety: The call is safe and does not require synchronization.
89        SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)
90    };
91
92    if let Err(e) = set_result {
93        log::warn!("Could not set DPI awareness context: {:?}. This is fine if you don't want to use DXGI capture or if you have already specified DPI awareness in the application manifest.", e);
94        Err(ObsError::InvalidOperation("Process is not DPI aware and could not set DPI awareness. DPI awareness is required for DXGI monitor capture however".into()))
95    } else {
96        Ok(())
97    }
98}
99
100impl<'a> MonitorCaptureSourceUpdater<'a> {
101    pub fn set_capture_method(mut self, method: ObsDisplayCaptureMethod) -> Result<Self, ObsError> {
102        if method == ObsDisplayCaptureMethod::MethodDXGI {
103            set_dpi_awareness_if_needed(self.runtime())?;
104        }
105        self.get_settings_updater()
106            .set_int_ref("method", method.to_i32().unwrap() as i64);
107
108        Ok(self)
109    }
110}
111
112impl MonitorCaptureSourceBuilder {
113    /// Sets the capture method for the monitor capture source.
114    /// If you want to use DXGI, it is required for your application to be DPI aware.
115    pub fn set_capture_method(mut self, method: ObsDisplayCaptureMethod) -> Self {
116        self.capture_method = Some(method);
117
118        self
119    }
120}
121
122pub type GeneralSourceRef = Arc<Box<dyn ObsSourceTrait>>;
123impl ObsSourceBuilder for MonitorCaptureSourceBuilder {
124    type T = MonitorCaptureSource;
125
126    fn build(self) -> Result<Self::T, ObsError>
127    where
128        Self: Sized,
129    {
130        if self.capture_method == Some(ObsDisplayCaptureMethod::MethodDXGI) {
131            set_dpi_awareness_if_needed(self.runtime())?;
132        }
133
134        let runtime = self.runtime.clone();
135        let obj_info = self.object_build()?;
136
137        let res = ObsSourceRef::new_from_info(obj_info, runtime)?;
138        MonitorCaptureSource::new(res)
139    }
140
141    fn add_to_scene(mut self, scene: &mut ObsSceneRef) -> Result<ObsSceneItemRef<Self::T>, ObsError>
142    where
143        Self: Sized,
144    {
145        // Because of a black screen bug, we need to set the method to WGC first and then update
146        self.get_settings_updater().set_int_ref(
147            "method",
148            ObsDisplayCaptureMethod::MethodWgc.to_i32().unwrap() as i64,
149        );
150
151        let method_to_set = self.capture_method;
152
153        let mut res = self.build()?;
154        let scene_item = scene.add_source(res.clone())?;
155
156        if let Some(method) = method_to_set {
157            res.create_updater()?
158                .set_capture_method(method)? //
159                .update()?;
160        }
161
162        Ok(scene_item)
163    }
164}
165
166impl_custom_source!(MonitorCaptureSource);